#!/usr/bin/python
# -*- coding: utf-8 -*-

u"""
gui_typedchoosers provides widgets specialized for datum types.
"""

__all__ = [
  u'CHOOSERS',
  u'GuiBoolChooser',
  u'GuiFloatChooser',
  u'GuiIntChooser',
  u'GuiPathChooser',
  u'GuiTypedChooser',
  u'GuiUnicodeChooser',
  u'make_chooser',
  ]

from PyQt4 import QtGui
from PyQt4.QtGui import QSizePolicy
from PyQt4.QtCore import SIGNAL


from _pylot.qtgui.interfaces.widgetinterface import WidgetInterface
from _pylot.qtgui.styles.policy import *
from _pylot.qtgui.misc import create_background_palette

from pylot.core import (Datum, FilePath, datum_name, list2, unicode2,
                           to_qt_tt, to_xml, ENCODING, Path, DirPath, FilePath)

class GuiTypedChooser(QtGui.QWidget, WidgetInterface):
  u"""
  - SIGNAL dataChanged [value]: this signal is sent when chooser value changes
  """

  def __init__(self, datum, book_id=None, handler=None):
    QtGui.QWidget.__init__(self)
    WidgetInterface.__init__(self, handler)

    self._datum = datum
    if book_id is None :
      book_id = datum.name
    self._book_id = book_id

    self._base_layout = QtGui.QHBoxLayout(self)
    self._base_layout.setContentsMargins(5, 0, 5, 0)

  def setEnabled(self, state):
    self._data_widget.setEnabled(state)
    self._label_widget.setEnabled(state)

  def setDisabled(self, state):
    self._data_widget.setDisabled(state)
    self._label_widget.setDisabled(state)

  def getDataWidget(self):
    u"""
    :returns: QWidget containing data (ie containing path selected by user.)
    """
    return self._data_widget

  def getDatum(self):
    return self._datum

  def getLabelWidget(self):
    u"""
    :returns: QWidget containing labels (text, tooltips, ...)
    """
    return self._label_widget

  def getValue(self):
    u"""
    :returns: current path
    """
    return self._datum.value

  def setValue(self, value):
    self._datum.value = value
    self.refresh()

  def resetDataToDefault(self):
    self.setValue(self._datum.default_value)

  def update(self, *args):
    u"""
    Updates datum object
    """
    self._updateData()

  def emitDataChangedSignal(self):
    self.emit(SIGNAL("dataChanged"), self._datum.value)

  def _getValue(self):
    raise NotImplementedError

  def _onDataChanged(self, *args):
    old_value = self._datum.value
    self._datum.value = self._getValue()
    if self._datum.value != old_value and self._datum.value is not None:
      self.emitDataChangedSignal()

  def _translate(self):
    label = self.getLabelWidget()
    alias = self.tr_(datum_name(self._datum))

    whatsthis = tr_(u'<p style="font-weight:600;">Modify value of "%(ALIAS)s"</p>') % dict(ALIAS=alias)
    whatsthis += tr_(u'<p>default value: %s<br/>') % to_qt_tt(self._datum.default_value)
    if self.handler.goodLevel(u'devel') :
      whatsthis += tr_(u'<p style="color:#646464;">Datum information:<br/>')
      whatsthis += tr_(u' - hexadecimal identifier : %s<br/>') % hex(id(self._datum))
      datum_type = str(type(self._datum)).decode(ENCODING)
      whatsthis += tr_(u' - type : %s<br/>') % to_xml(datum_type)
      whatsthis += tr_(u' - name : %s<br/>') % self._datum.name
      whatsthis += tr_(u' - nature : %s<br/>') % to_xml(self._datum.nature)
      whatsthis += tr_(u' - value : %s<br/>') % to_xml(self._datum.value)
      whatsthis += tr_(u' - default value : %s<br/>') % to_xml(self._datum.default_value)
      try:
        level = self._datum.level
      except AttributeError :
        pass
      else :
        whatsthis += tr_(u' - level : %s<br/>') % level
      whatsthis += u'</p>'

    if self._datum.tooltip is not None :
      tooltip = self.tr_(self._datum.tooltip)
      self.setToolTip(tooltip)
      label.setToolTip(tooltip)
    self.setWhatsThis(whatsthis)
    label.setWhatsThis(whatsthis)
    label.setText(alias)

class GuiIntChooser (GuiTypedChooser):

  def __init__(self, datum, book_id=None, handler=None, range=(0, 1000000)):
    GuiTypedChooser.__init__(self, datum, book_id, handler)

    self._label_widget = QtGui.QLabel()

    data = QtGui.QSpinBox()
    data.setRange(*range)
    data.setValue(datum.value)
    self._data_widget = data

    spacer = QtGui.QLabel()
    spacer.setSizePolicy(QSizePolicy(Fixed, Maximum))
    spacer.setMinimumWidth(60)

    self._base_layout.addWidget(self._label_widget)
    self._base_layout.addWidget(self._data_widget)
    self._base_layout.addWidget(spacer)
    try :
      self._base_layout.setStretch(0, 1)
      self._base_layout.setStretch(1, 3)
    except AttributeError : # Qt < 4.5
      pass

    self._createConnexions()
    self._translate()
    self._refresh()

  def _createConnexions(self):
    self.connect(self._data_widget, SIGNAL('valueChanged(int)'),
                 self._onDataChanged)

  def _getValue(self):
    return self._data_widget.value()

  def _refresh(self):
    self._data_widget.setValue(self._datum.value)

class GuiBoolChooser (GuiTypedChooser):

  def __init__(self, datum, book_id=None, handler=None):
    GuiTypedChooser.__init__(self, datum, book_id, handler)

    data = QtGui.QCheckBox()
    data.setChecked(bool(datum.value))

    self._data_widget = data
    self._label_widget = data

    self._base_layout.addWidget(data)

    self._createConnexions()
    self._translate()
    self._refresh()

  def _createConnexions(self):
    self.connect(self._data_widget, SIGNAL('stateChanged(int)'),
                 self._onDataChanged)

  def _getValue(self):
    return self._data_widget.isChecked()

  def _refresh(self):
    self._data_widget.setChecked(self._datum.value)

  def setCheckable(self, state):
      self._data_widget.setCheckable(state)

class GuiFloatChooser (GuiTypedChooser):

  def __init__(self, datum, book_id=None, handler=None, range=(0, 1000000)):
    GuiTypedChooser.__init__(self, datum, book_id, handler)

    self._label_widget = QtGui.QLabel()

    data = QtGui.QDoubleSpinBox()
    data.setDecimals(6)
    data.setRange(*range)
    self._data_widget = data

    spacer = QtGui.QLabel()
    spacer.setSizePolicy(QSizePolicy(Fixed, Maximum))
    spacer.setMinimumWidth(60)

    self._base_layout.addWidget(self._label_widget)
    self._base_layout.addWidget(self._data_widget)
    self._base_layout.addWidget(spacer)
    try:
      self._base_layout.setStretch(0, 1)
      self._base_layout.setStretch(1, 3)
    except AttributeError : # Qt < 4.5
      pass

    self._createConnexions()
    self._translate()
    self._refresh()

  def _createConnexions(self):
    self.connect(self._data_widget, SIGNAL('valueChanged(double)'),
                 self._onDataChanged)

  def _getValue(self):
    return self._data_widget.value()

  def _refresh(self):
    self._data_widget.setValue(self._datum.value)

class _GuiUnicodeChooser (GuiTypedChooser):

  def __init__(self, datum, book_id=None, handler=None, editable=True):
    GuiTypedChooser.__init__(self, datum, book_id, handler)

    data = QtGui.QComboBox()
    data.setEditable(editable)
    data.setSizeAdjustPolicy(QtGui.QComboBox.AdjustToMinimumContentsLength)
    self._data_widget = data
    self._data_widget.setSizePolicy(QSizePolicy(MinimumExpanding, Preferred))

    self._refreshValue()
    self._refreshValueList()

  def _createConnexions(self):
    if self._data_widget.isEditable():
      self.connect(self._data_widget.lineEdit(), SIGNAL('editingFinished()'),
                   self._onDataChanged)

    self.connect(self._data_widget, SIGNAL('currentIndexChanged(int)'),
                 self._onDataChanged)

  def _deleteConnexions(self):
    if self._data_widget.isEditable():
      self.disconnect(self._data_widget.lineEdit(), SIGNAL('editingFinished()'),
                      self._onDataChanged)

    self.disconnect(self._data_widget, SIGNAL('currentIndexChanged(int)'),
                    self._onDataChanged)

  def _getValue(self):
    return unicode(self._data_widget.currentText())

  def _refresh(self):
    old_value = self._datum.value

    self._deleteConnexions()

    self._data_widget.clear()

    self._datum.value = old_value

    self._refreshValue() # sets value
    self._refreshDefautlValue() # adds default_value in combobox
    self._refreshValueList() # adds values of value_list in combobox

    self._createConnexions()

  def _refreshValue(self):
    u"""
    If text yet in combobox, sets it to current index, else, adds it.
    Then, emit dataChanged signal if data changes.
    """
    text = self._datum.value
    if text is not None :
      text = unicode(text)
      # if current text is yet the good one, return
      if unicode(self._data_widget.currentText()) == text :
        return
      # Look if text yet in combobox
      for index in range(self._data_widget.count()):
        if unicode(self._data_widget.itemText(index)) == text:
          self._data_widget.setCurrentIndex(index)
          self._onDataChanged()
          return

      self._data_widget.insertItem(0, text)
      self._data_widget.setCurrentIndex(0)
      self._onDataChanged()

  def _refreshDefautlValue(self):
    if self._datum.default_value :
      self.addValue(self._datum.default_value)

  def _refreshValueList(self):
    for val in list2(self._datum.value_list) :
      self.addValue(val)

  def addValue(self, text):
    u"""
    If text not in combobox, adds it, else do nothing.
    This method adds text at the end of the list.
    Please use "setValue" to change current value
    """
    if text is not None :
      text = unicode(text)
      # Look if text yet in combobox
      for index in range(self._data_widget.count()):
        if unicode(self._data_widget.itemText(index)) == text:
          return

      self._data_widget.addItem(text)

class GuiUnicodeChooser (_GuiUnicodeChooser):
  u"""
  A unicode string selector.
  
  ** SIGNALS **

  - SIGNAL dataChanged [value] : this signal is sent when line editing is
    finished. value is the new value
  """

  def __init__(self, datum, book_id=None, handler=None, editable=True):
    _GuiUnicodeChooser.__init__(self, datum, book_id, handler, editable=editable)

    self._label_widget = QtGui.QLabel()

    spacer = QtGui.QLabel()
    spacer.setSizePolicy(QSizePolicy(Fixed, Maximum))
    spacer.setMinimumWidth(60)

    self._base_layout.addWidget(self._label_widget)
    self._base_layout.addWidget(self._data_widget)
    self._base_layout.addWidget(spacer)

    try :
      self._base_layout.setStretch(0, 1)
      self._base_layout.setStretch(1, 3)
    except AttributeError : # Qt < 4.5
      pass

    self._createConnexions()
    self._translate()

class GuiPathChooser (_GuiUnicodeChooser):
  u"""
  GuiPathChooser provides a file or directory selector based on a datum object.

  Example of use :
  
  >>> from PyQt4.QtGui import QApplication
  >>> app = QApplication([])
  >>> from pylot.core import Datum, FilePath
  >>> d = Datum(u'd', FilePath, u'/', u'/home', alias=u'Mon Path')
  >>> path_chooser = GuiPathChooser(d)
  >>> path_chooser.show()
  >>> ret = app.exec_()

  Once created, you can get value with 
  :meth:`~pylot.qtgui.GuiPathChooser.getValue`.
  
  If you want to update original datum (Datum passed during GuiPathChooser
  initialization) you must do it explicitly by calling 
  :meth:`~pylot.qtgui.GuiPathChooser.updateData`.

  If you want to customize widget, you should have a look to
  :meth:`~pylot.qtgui.GuiPathChooser.getPushButton` and
  :meth:`~pylot.qtgui.GuiPathChooser.getDataWidget` methods.
  
  .. seealso ::
  
    :class:`~pylot.core.Datum`, :class:`~pylot.core.Path`
    :class:`~pylot.core.FilePath` and :class:`~pylot.core.DirPath`

  """
  ButtonNo = 0
  ButtonLeft = 1
  ButtonRight = 2

  def __init__(self, datum, book_id=None, handler=None, scroll_area=False,
               style=ButtonRight, editable=True, check_exists=False):
    u"""
    :param scroll_area: if True, set path chooser in a scroll area.
    """
    self._check_exists = check_exists
    self.initial_path = Path(u'.')
    _GuiUnicodeChooser.__init__(self, datum, book_id, handler, editable=editable)

    self._label_widget = QtGui.QLabel()
    self._push_button = QtGui.QPushButton(self)
    self._push_button.setSizePolicy(QSizePolicy(Fixed, Maximum))
    self._push_button.setMinimumWidth(60)
    pb_id = u'_'.join([u'QPushButton', self._book_id, datum.name])
    self._push_button.setObjectName(pb_id)
    self._push_button.connect(self._push_button, SIGNAL("clicked()"),
                              self.chooseDir)

    if scroll_area :
      content = QtGui.QWidget()
      self._addWidgets(content, style)

      layout = QtGui.QHBoxLayout(content)
      self._addWidgets()

      scroll_area = QtGui.QScrollArea(self)
      scroll_area.setFrameShape(QtGui.QFrame.NoFrame)
      scroll_area.setWidgetResizable(True)
      scroll_area.setSizePolicy(QSizePolicy(Maximum, Preferred))
      scroll_area.setWidget(content)
      self._base_layout.addWidget(scroll_area)
    else :
      self._addWidgets(self._base_layout, style)

    self._createConnexions()
    self._translate()
    self._decorate()

  def _addWidgets(self, layout, style):
    if style == self.ButtonLeft :
      layout.addWidget(self._push_button)
      layout.addWidget(self._label_widget)
      layout.addWidget(self._data_widget)
    elif style == self.ButtonRight :
      layout.addWidget(self._label_widget)
      layout.addWidget(self._data_widget)
      layout.addWidget(self._push_button)
      try:
        layout.setStretch(0, 1)
        layout.setStretch(1, 3)
      except AttributeError : # Qt < 4.5
        pass
    elif style == self.ButtonNo :
      layout.addWidget(self._data_widget)

  def _onDataChanged(self, *args):
    _GuiUnicodeChooser._onDataChanged(self, *args)
    if self._check_exists :
      if self._datum.value is None :
        palette = create_background_palette(QtGui.QColor(255, 100, 100))
        self._data_widget.setPalette(palette)
      elif self._datum.value.exists() :
        palette = create_background_palette(QtGui.QColor(255, 255, 255))
        self._data_widget.setPalette(palette)
      else :
        palette = create_background_palette(QtGui.QColor(255, 100, 100))
        self._data_widget.setPalette(palette)

  def _decorate(self):
    self._push_button.setIcon(self._icon(u'dir.png'))

  def _translate(self):
    GuiTypedChooser._translate(self)
    msg = tr_(u"If no directory in combobox agrees, click here to select" \
            u"existing directory.")
    self._push_button.setToolTip(msg)
    self._label_widget.setText(self.tr_(datum_name(self._datum)))

  def getPushButton(self):
    u"""
    Returns the push button widget which launches the path selector
    
    :rtype: QtGui.QPushButton
    """
    return self._push_button

  def chooseDir(self):
    u"""
    Method called when user clicks on "select path" button.
    Create and display a path selector.
    
    The type of path selector (directory or file) depends on datum's nature.
    """
    pb_id = self.sender().objectName()
    datum = self._datum
    datum_id = datum.name

    if self.initial_path :
      initial_path = self.initial_path
    else :
      initial_path = datum.value

    if issubclass(datum.nature, FilePath) :
      rep = QtGui.QFileDialog.getOpenFileName (self,
        unicode2(datum.alias), initial_path)
    else :
      rep = QtGui.QFileDialog.getExistingDirectory (self,
        unicode2(datum.alias), initial_path)

    if unicode(rep) :
      self.setValue(unicode(rep))

CHOOSERS = {
  bool:GuiBoolChooser,
  int:GuiIntChooser,
  float:GuiFloatChooser,
  unicode:GuiUnicodeChooser,
  Path:GuiPathChooser,
  DirPath:GuiPathChooser,
  FilePath:GuiPathChooser
  }

def make_chooser(datum):
  try :
    chooser_class = CHOOSERS[datum.nature]
  except KeyError :
    return None
  else :
    return chooser_class(datum)

if __name__ == '__main__' :
  import doctest
  doctest.testmod()

  from PyQt4.QtGui import QApplication
  from pylot.core import Datum, FilePath
  app = QApplication([])

  d1 = Datum(u'file', FilePath, u'/', u'/home', tooltip=u'Un chemin', alias=u'Mon Path')
  chooser1 = GuiPathChooser(d1)
  chooser1.show()

  d2 = Datum(u'int', int, 1, tooltip=u'Un entier', alias=u'Mon entier')
  chooser2 = GuiIntChooser(d2)
  chooser2.show()

  d3 = Datum(u'bool', bool, 1, tooltip=u'Un booléen', alias=u'Mon booléen')
  chooser3 = GuiBoolChooser(d3)
  chooser3.show()

  d4 = Datum(u'float', float, 1, tooltip=u'Un décimal', alias=u'Mon décimal')
  chooser4 = GuiFloatChooser(d4)
  chooser4.show()

  d5 = Datum(u'unicode', unicode, u'¡ Ciao !', tooltip=u'Une chaîne', alias=u'Ma chaîne')
  chooser5 = GuiUnicodeChooser(d5)
  chooser5.show()

  ret = app.exec_()
